package com.demo2do.fms.admin.service.supplier.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.demo2do.core.entity.exception.BusinessException;
import com.demo2do.core.entity.page.Page;
import com.demo2do.core.persistence.annotation.Dao;
import com.demo2do.core.utils.DateUtils;
import com.demo2do.fms.admin.Constants;
import com.demo2do.fms.admin.dao.supplier.DriverDao;
import com.demo2do.fms.admin.dao.supplier.SupplierDao;
import com.demo2do.fms.admin.dao.supplier.SupplierRelationDao;
import com.demo2do.fms.admin.dao.supplier.TrailerEventDao;
import com.demo2do.fms.admin.dao.vehicle.RemoteTransMeansDao;
import com.demo2do.fms.admin.dao.vehicle.TrailerDao;
import com.demo2do.fms.admin.dao.vehicle.TrailerModifiedVersionDao;
import com.demo2do.fms.admin.dao.vehicle.VehicleDao;
import com.demo2do.fms.admin.dao.vehicle.VehicleSourceDao;
import com.demo2do.fms.admin.entity.report.supplier.TrailerReportSearch;
import com.demo2do.fms.admin.entity.search.supplier.TrailerSearch;
import com.demo2do.fms.admin.entity.security.Login;
import com.demo2do.fms.admin.entity.upload.UploadResult;
import com.demo2do.fms.admin.handler.FileHandler;
import com.demo2do.fms.admin.handler.VamHandler;
import com.demo2do.fms.admin.handler.support.FileStorage;
import com.demo2do.fms.admin.handler.support.FmsJdbcSupport;
import com.demo2do.fms.admin.service.DataServiceImpl;
import com.demo2do.fms.admin.service.supplier.TrailerService;
import com.demo2do.fms.admin.utils.DateParseUtils;
import com.demo2do.fms.entity.authority.Organization;
import com.demo2do.fms.entity.supplier.common.Supplier;
import com.demo2do.fms.entity.vehicle.AuditStatus;
import com.demo2do.fms.entity.vehicle.DeclareStatus;
import com.demo2do.fms.entity.vehicle.ModifiedStatus;
import com.demo2do.fms.entity.vehicle.source.LockStatus;
import com.demo2do.fms.entity.vehicle.source.LockType;
import com.demo2do.fms.entity.vehicle.source.RemoteTransMeans;
import com.demo2do.fms.entity.vehicle.source.VehicleSource;
import com.demo2do.fms.entity.vehicle.source.VehicleSourceEvent;
import com.demo2do.fms.entity.vehicle.source.VehicleSourceEventType;
import com.demo2do.fms.entity.vehicle.source.VehicleSourceLockRecord;
import com.demo2do.fms.entity.vehicle.tractor.Tractor;
import com.demo2do.fms.entity.vehicle.trailer.Trailer;
import com.demo2do.fms.entity.vehicle.trailer.TrailerEvent;
import com.demo2do.fms.entity.vehicle.trailer.TrailerEventType;
import com.demo2do.fms.entity.vehicle.trailer.TrailerModifiedVersion;
import com.demo2do.fms.entity.vehicle.trailer.TrailerType;
import com.demo2do.fms.entity.vehicle.trailer.TrailerVehicleType;

/**
 *
 * @author Ark
 *
 */
@Service("trailerService")
public class TrailerServiceImpl extends DataServiceImpl<Trailer> implements TrailerService {

    private static final Logger logger = LoggerFactory.getLogger(TrailerServiceImpl.class);
    
    @Dao
    private TrailerDao trailerDao;
    
    @Dao
    private SupplierDao supplierDao;
    
    @Dao
    private SupplierRelationDao supplierRelationDao;
    
    @Dao
    private TrailerModifiedVersionDao trailerModifiedVersionDao;
    
    @Dao
    private VehicleSourceDao vehicleSourceDao;
    
    @Dao
    private VehicleDao vehicleDao;
    
    @Dao
    private RemoteTransMeansDao remoteTransMeansDao;
    
    @Dao
    private DriverDao driverDao;
    
    @Dao
    private TrailerEventDao trailerEventDao;
    
    @Autowired
    private FileHandler fileHandler;
    
    @Autowired
    private FmsJdbcSupport fmsJdbcSupport;
    
    @Autowired
    private VamHandler vamHandler;
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#count(com.demo2do.fms.admin.entity.security.Login, com.demo2do.fms.entity.vehicle.AuditStatus)
     */
    public int count(Login login, AuditStatus status) {
        
        if (login.isSupplier()) {
            
            Supplier supplier = supplierDao.loadSupplier(login.getSupplierId());
            return trailerDao.count(supplier, status);
            
        } else {
            return trailerDao.count(status);
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#list(com.demo2do.fms.admin.entity.security.Login, com.demo2do.fms.entity.vehicle.AuditStatus, com.demo2do.core.entity.page.Page)
     */
    public List<Trailer> list(Login login, AuditStatus status, Page page) {
        
        page.calculate();
        
        if (login.isSupplier()) {

            Supplier supplier = supplierDao.loadSupplier(login.getSupplierId());
            return trailerDao.list(supplier, status, page.getBeginIndex(), page.getEveryPage());
            
        } else {
            return trailerDao.list(status, page.getBeginIndex(), page.getEveryPage());
        }

        
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#count(com.demo2do.fms.admin.entity.search.supplier.TrailerSearch)
     */
    public int count(TrailerSearch search) {
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        return genericDaoSupport.count(sentence, parameters);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#search(com.demo2do.fms.admin.entity.search.supplier.TrailerSearch, com.demo2do.core.entity.page.Page)
     */
    @SuppressWarnings("unchecked")
    public List<Trailer> search(TrailerSearch search, Page page) {
        
        // 1. build sentence
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        
        // 2. do query
        List<Trailer> trailers = genericDaoSupport.searchForList(sentence, parameters, page.getBeginIndex(), page.getEveryPage());
        page.calculate();
        return trailers;
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#create(com.demo2do.fms.entity.vehicle.Trailer, com.demo2do.fms.admin.entity.security.Login)
     */
    public void create(Trailer trailer, Login login) {
        
        Date now = new Date();
        Long operator = login.getId();
        
        // 1. validate
        if (!login.isSupplier()) {
            throw BusinessException.newInstance("500", "非供方，无法创建车辆");
        }
        
        String validMessage = this.checkUniqueField(trailer.getBrandCNCode());
        if (StringUtils.isNotBlank(validMessage)) {
            throw BusinessException.newInstance("400", validMessage);
        }
        
        // 2. save trailer
        Supplier supplier = supplierDao.loadSupplier(login.getSupplierId());
        Organization organization = genericDaoSupport.load(Organization.class, supplier.getOrgId());
        
        trailer = trailer.initialize(organization, supplier, now, operator);
        genericDaoSupport.save(trailer);
        
        // 3. save trailer_event
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.CREATE, operator));
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#upload(org.springframework.web.multipart.MultipartFile, com.demo2do.fms.admin.entity.security.Login)
     */
    @SuppressWarnings("unchecked")
    public List<UploadResult> upload(MultipartFile file, Login login) {
        
        try {

            // 1. save file
            FileStorage fileStorage = new FileStorage("vehicle/upload", Constants.TRAILER_BATCH_UPLOAD, FilenameUtils.getExtension(file.getOriginalFilename()));
            fileHandler.save(fileStorage, file.getInputStream());
            
            // 2. save trailer && trailer_event
            Map<String, Object> result = this.parse(file, login);
            List<Trailer> trailers = (List<Trailer>) result.get("trailers");
            for (Trailer trailer : trailers) {
                
                // 2.1 save trailer
                genericDaoSupport.save(trailer);
                
                // 2.2 save trailer_event
                genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.CREATE, login.getId()));
                
            }
            
            // 3. return upload result
            logger.trace("#upload() - batch import trailer success, number = [{}]", trailers.size());
            return (List<UploadResult>) result.get("uploadResults");
            
        } catch (Exception e) {
            logger.error("#upload() - batch import trailer fail, exception = [{}]", e);
            throw BusinessException.newInstance("500", "批量上传失败，" + e.getMessage());
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#exist(java.lang.String)
     */
    public boolean exist(String brandCNCode) {
        Trailer trailer = trailerDao.getByBrandCNCode(brandCNCode);
        return trailer != null;
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#edit(com.demo2do.fms.entity.vehicle.Trailer, java.lang.Long)
     */
    public void edit(Trailer trailer, Long operator) {
        genericDaoSupport.update(trailer.operate(new Date(), operator));
    }
    
    /**
     * 校验挂车唯一性
     * 
     * @param brandCnCode
     * @return
     */
    private String checkUniqueField(String brandCnCode) {
        
        // 1. check trailer brand cn_code
        if (StringUtils.isNotBlank(brandCnCode)) {
            Trailer trailer = trailerDao.getByBrandCNCode(brandCnCode);
            if (trailer != null) {
                Supplier supplier = trailer.getSupplier();
                return "该牵引车中文车牌号已存在！车辆所属供方：" + supplier.getCode();
            }
        }
        
        return null;
        
    }
    
    /**
     * @param file
     * @param login
     * @return
     */
    public Map<String, Object> parse(MultipartFile file, Login login) {
        
        Map<String, Object> result = new HashMap<String, Object>();
        
        List<Trailer> trailers = new ArrayList<Trailer>();
        List<UploadResult> uploadResults = new ArrayList<UploadResult>();
        
        // 0. prepare data
        Long operator = login.getId();
        Supplier supplier = supplierDao.loadSupplier(login.getSupplierId());
        Organization organization = genericDaoSupport.load(Organization.class, supplier.getOrgId());
        
        DataFormatter formatter = new DataFormatter();
        
        try {
            
            logger.trace("#parse() - Starting parsing upload file.");
    
            // 1. get workbook
            Workbook workbook = WorkbookFactory.create(file.getInputStream());
            
            // 2. get sheet and rows
            Sheet sheet = workbook.getSheetAt(0);
            Iterator<Row> rows = sheet.iterator();
            
            // 3. read rows and transform to trailer
            while (rows.hasNext()) {
                
                Row row = rows.next();
                int rowNum = row.getRowNum();
                UploadResult uploadResult = null;
                
                if (rowNum < 2) {
                    continue;
                }
                
                String floorLord = formatter.formatCellValue(row.getCell(0));       // 产证所有者
    
                String trailerTypeText = formatter.formatCellValue(row.getCell(1));         // 挂车类型
                TrailerType trailerType = TrailerType.descriptionOf(trailerTypeText);
                
                String brandCNCode = formatter.formatCellValue(row.getCell(2));    // 挂车中文牌号
                if (StringUtils.isBlank(brandCNCode)) {      // 判断Excel空行情况
                    continue;
                }
                if (!Pattern.matches("[\u4e00-\u9fa5]{1}[\u4e00-\u9fa5a-zA-Z0-9]*", brandCNCode)) {
                    uploadResults.add(new UploadResult(rowNum, "挂车中文牌号填写与模板不符"));
                    continue;
                }
                
                String vincode = formatter.formatCellValue(row.getCell(3));            // 挂车vin码
                
                String lengthText = formatter.formatCellValue(row.getCell(4));         // 挂车长
                if (StringUtils.isBlank(lengthText) || !NumberUtils.isCreatable(lengthText)) {
                    uploadResults.add(new UploadResult(rowNum, "挂车长填写与模板不符"));
                    continue;
                }
                BigDecimal length = new BigDecimal(lengthText);
                
                String widthText = formatter.formatCellValue(row.getCell(5));         // 挂车宽
                if (StringUtils.isBlank(widthText) || !NumberUtils.isCreatable(widthText)) {
                    uploadResults.add(new UploadResult(rowNum, "挂车宽填写与模板不符"));
                    continue;
                }
                BigDecimal width = new BigDecimal(widthText);
                
                String heightText = formatter.formatCellValue(row.getCell(6));         // 挂车高
                if (StringUtils.isBlank(heightText) || !NumberUtils.isCreatable(heightText)) {
                    uploadResults.add(new UploadResult(rowNum, "挂车高填写与模板不符"));
                    continue;
                }
                BigDecimal height = new BigDecimal(heightText);
                
                String trainloadText = formatter.formatCellValue(row.getCell(7));       // 装载量
                if (StringUtils.isBlank(trainloadText) || !NumberUtils.isCreatable(trainloadText)) {
                    uploadResults.add(new UploadResult(rowNum, "装载量填写与模板不符"));
                    continue;
                }
                BigDecimal trainload = new BigDecimal(trainloadText);
                
                String annualInspectionDateText = formatter.formatCellValue(row.getCell(8));       // 年审时间
                if (StringUtils.isBlank(annualInspectionDateText) || !DateParseUtils.validDate(annualInspectionDateText, Constants.DATE_YYYYMM) || !StringUtils.equals(annualInspectionDateText, DateFormatUtils.format(com.demo2do.core.utils.DateUtils.parseDate(annualInspectionDateText, "yyyyMM"), "yyyyMM"))) {
                    uploadResults.add(new UploadResult(rowNum, "挂车年检有效期至日期填写与模板不符"));
                    continue;
                }
                Date annualInspectionDate = DateUtils.parseDate(annualInspectionDateText, Constants.DATE_YYYYMM);
                
                String axlesText = formatter.formatCellValue(row.getCell(9));       // 轴数
                if (!NumberUtils.isCreatable(axlesText)) {
                    uploadResults.add(new UploadResult(rowNum, "轴数填写与模板不符"));
                    continue;
                }
                BigDecimal axles = new BigDecimal(axlesText);
                
                TrailerVehicleType vehicleType = TrailerVehicleType.descriptionOf(formatter.formatCellValue(row.getCell(10)));      // 车辆类型（行驶证）
                
                String brand = formatter.formatCellValue(row.getCell(11));       // 品牌
                
                // 4. construct trailer
                Trailer trailer = new Trailer(floorLord, trailerType, brandCNCode, vincode, length, width, height, trainload, annualInspectionDate, axles, vehicleType, brand, organization, supplier, operator);
                
                // 5.1 check required field
                uploadResult = this.checkRequiredField(rowNum, trailer);
                if (uploadResult != null) {
                    uploadResults.add(uploadResult);
                    continue;
                }
                
                // 5.2 check unique field
                uploadResult = this.checkUniqueField(rowNum, brandCNCode);
                if (uploadResult != null) {
                    uploadResults.add(uploadResult);
                    continue;
                }
                
                // 5.3 check unique_field of excel
                uploadResult = this.checkUniqueFieldOfExcel(trailers, rowNum, brandCNCode);
                if (uploadResult != null) {
                    uploadResults.add(uploadResult);
                    continue;
                }
                    
                trailers.add(trailer);
                
            }
            
            logger.trace("#parse() - Finishing parsing file with {} records.", trailers.size());
            
        } catch (Exception e) {
            logger.error("#parse() - Finishing parsing file with errors.", e);
        }
        
        result.put("trailers", trailers);
        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(uploadResults)) {
            result.put("uploadResults", uploadResults);
        }
        
        return result;
        
    }

    /**
     * @param rowNum
     * @param trailer
     * @return
     */
    private UploadResult checkRequiredField(int rowNum, Trailer trailer) {
        
        if(StringUtils.isBlank(trailer.getBrandCNCode())) {
            return new UploadResult(rowNum, "挂车中文车牌号填写与模板不符");
        }else if (StringUtils.isBlank(trailer.getFloorLord())) {
            return new UploadResult(rowNum, "挂车产证所有者填写与模板不符");
        } else if (trailer.getTrailerType() == null) {
            return new UploadResult(rowNum, "挂车类型填写与模板不符");
        } else if (StringUtils.isBlank(trailer.getVincode())) {
            return new UploadResult(rowNum, "挂车VIN码填写与模板不符");
        } else if (trailer.getVehicleType() == null) {
            return new UploadResult(rowNum, "车辆类型（行驶证）填写与模板不符");
        } else if (StringUtils.isBlank(trailer.getBrand())) {
            return new UploadResult(rowNum, "品牌填写与模板不符");
        }
        
        return null;
        
    }

    /**
     * @param rowNum
     * @param brandCNCode
     * @param simCard
     * @return
     */
    private UploadResult checkUniqueField(int rowNum, String brandCNCode) {
    
        Trailer trailer = trailerDao.getByBrandCNCode(brandCNCode);
        if (trailer != null) {
            Supplier supplier = trailer.getSupplier();
            String supplierInfo = com.demo2do.core.utils.StringUtils.connect(supplier.getCode(), " - ", supplier.getName());
            return new UploadResult(rowNum, "挂车中文牌号已存在", supplierInfo, trailer.getBrandCNCode());
        }
        
        return null;
        
    }
    
    /**
     * @param trailers
     * @param rowNum
     * @param brandCNCode
     * @return
     */
    @SuppressWarnings("unchecked")
    private UploadResult checkUniqueFieldOfExcel(List<Trailer> trailers, int rowNum, String brandCNCode) {
        
        if (CollectionUtils.isEmpty(trailers)) {
            return null;
        }
        
        List<String> brandCNCodes = com.demo2do.core.utils.CollectionUtils.extractPropertyList(trailers, "brandCNCode");
        if (brandCNCodes.contains(brandCNCode)) {
            return new UploadResult(rowNum, "请不要填写相同中文车牌号");
        }
        
        return null;
        
    }

    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#audit(com.demo2do.fms.entity.vehicle.Trailer, java.lang.Long)
     */
    public void audit(Trailer trailer, Long operator) {
        
        // 1. validate
        String auditMessage = trailer.auditMessage();
        if (StringUtils.isNotBlank(auditMessage)) {
            throw BusinessException.newInstance("500", auditMessage);
        }
        
        auditMessage = this.checkUniqueField(trailer);
        if (StringUtils.isNotBlank(auditMessage)) {
            throw BusinessException.newInstance("500", auditMessage);
        } 
        
        // 2. submit audit
        genericDaoSupport.update(trailer.audit(operator));
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.AUDIT, operator));
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#batchAudit(java.util.List, java.lang.Long)
     */
    public int batchAudit(List<Long> ids, Long operator) {
        
        int failNumber = 0;
        List<Trailer> trailers = trailerDao.list(ids);

        for (Trailer trailer : trailers) {
            
            try {
                
                this.audit(trailer, operator);
                
            } catch (Exception e) {
                failNumber++;
                logger.error("#batchAudit() - audit failed, tractorId=[{}], exception=[{}]", trailer.getId(), e);
            }
            
        }
        
        return failNumber;
        
    }
    
    /**
     * @param trailer
     * @return
     */
    private String checkUniqueField(Trailer trailer) {
        
        // 中文车牌号
        int count = trailerDao.countByBrandCNCode(trailer.getBrandCNCode());
        if (count > 1) {
            return "车辆中文车牌号重复";
        }
        
        return null;
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#delete(com.demo2do.fms.entity.vehicle.Trailer, java.lang.Long)
     */
    public void delete(Trailer trailer, Long operator) {
        
        Date now = new Date();
        
        // 1. delete trailer
        genericDaoSupport.update(trailer.disable(operator));
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.DELETE, operator));
        
        if (trailer.isBind()) {
            
            // 2. delete vehicle_source
            VehicleSource vehicleSource = vehicleSourceDao.loadByTrailer(trailer.getId());
            genericDaoSupport.update(vehicleSource.disable(operator).operate(now, operator));
            genericDaoSupport.save(new VehicleSourceEvent(vehicleSource, VehicleSourceEventType.UNBIND, operator));
            
            // 3. unbind tractor
            Tractor tractor = genericDaoSupport.load(Tractor.class, vehicleSource.getTractorId());
            genericDaoSupport.update(tractor.unbind().operate(now, operator));
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#prepareChange(com.demo2do.fms.entity.vehicle.Trailer)
     */
    public TrailerModifiedVersion prepareChange(Trailer trailer) {
        
        // 1. load最新未审核版本号
        TrailerModifiedVersion lastedVersion = trailerModifiedVersionDao.load(trailer.getId(), ModifiedStatus.AUDITING);
        int version = lastedVersion == null ? 1 : lastedVersion.getVersionSorting() + 1;
        
        // 2. create version by tractor
        TrailerModifiedVersion currentVersion = new TrailerModifiedVersion(trailer, version);
        genericDaoSupport.save(currentVersion);
        
        return currentVersion;
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#change(com.demo2do.fms.entity.vehicle.Trailer, com.demo2do.fms.entity.vehicle.TrailerModifiedVersion, java.lang.Long)
     */
    public void change(Trailer trailer, TrailerModifiedVersion version, Long operator) {
    
        // 1. change trailer
        genericDaoSupport.update(trailer.change(operator));
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.CHANGE, operator));
        
        // 2. update version
        genericDaoSupport.update(version);
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#loadModifiedVersion(com.demo2do.fms.entity.vehicle.Trailer)
     */
    public TrailerModifiedVersion loadModifiedVersion(Trailer trailer) {
        return trailerModifiedVersionDao.load(trailer.getId(), ModifiedStatus.AUDITING);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#transfer(com.demo2do.fms.entity.vehicle.Trailer, com.demo2do.fms.entity.supplier.common.Supplier, java.lang.Long)
     */
    public void transfer(Trailer trailer, Supplier supplier, Long operator) {
        
        Date now = new Date();
        Organization organization = genericDaoSupport.load(Organization.class, supplier.getOrgId());
        
        // 1. transfer trailer
        genericDaoSupport.update(trailer.transfer(supplier, organization, operator));
        
        // 2. save trailer
        trailerEventDao.update(supplier, trailer);
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.TRANSFER, operator));
        
        // 3. handle trailer、vehicle_source
        if (trailer.isBind()) {
            
            VehicleSource vehicleSource = vehicleSourceDao.loadByTrailer(trailer.getId());      // 删除整车
            genericDaoSupport.update(vehicleSource.disable(operator).operate(now, operator));
            genericDaoSupport.save(new VehicleSourceEvent(vehicleSource, VehicleSourceEventType.UNBIND, operator));
            
            Tractor tractor = genericDaoSupport.load(Tractor.class, vehicleSource.getTractorId());  // 解绑牵引车
            genericDaoSupport.update(tractor .unbind().operate(now, operator));
            
            if (vehicleSource.couldLock()) {                                                // 解除司机和车辆绑定关系
                this.lockVehicle(vehicleSource, "挂车解绑，调度异常锁定", LockType.SHIPMENT_EXCEPTION, operator, "挂车解绑，调度异常锁定");
            }
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#out(com.demo2do.fms.entity.vehicle.Trailer, java.lang.Long)
     */
    public void out(Trailer trailer, Long operator) {
        
        Date now = new Date();
        
        // 1. 判断是否存在已同步整车
        VehicleSource vehicleSource = vehicleSourceDao.loadByTrailer(trailer.getId());
        if (vehicleSource != null && vehicleSource.couldLock()) {
            this.lockVehicle(vehicleSource, "挂车解绑，调度异常锁定", LockType.SHIPMENT_EXCEPTION, operator, "挂车解绑，调度异常锁定");
        }
        
        // 2.1 删除整车
        if (trailer.isBind()) {
            
            genericDaoSupport.update(vehicleSource.out(operator));
            genericDaoSupport.save(new VehicleSourceEvent(vehicleSource, VehicleSourceEventType.UNBIND, operator));
            
            // 2.2 解绑牵引车
            Tractor tractor = genericDaoSupport.load(Tractor.class, vehicleSource.getTractorId());
            genericDaoSupport.update(tractor.unbind().operate(now, operator));
            
        }
        
        // 3. 删除挂车
        genericDaoSupport.update(trailer.out(operator));
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.OUT, operator));
        
    }
    
    /**
     * @param vehicleSource
     * @param reason
     * @param type
     * @param operator
     * @param description
     */
    private void lockVehicle(VehicleSource vehicleSource, String reason, LockType type, Long operator, String description) {
        
        Date now = new Date();
        
        // 1. lock fms vehicle
        genericDaoSupport.update(vehicleSource.doLock(reason, type, operator, now));
        
        String vehicleCode = vehicleSource.getCode();
        RemoteTransMeans remoteTransMeans = remoteTransMeansDao.loadByCode(vehicleCode);
        
        if(remoteTransMeans == null) {
            throw new BusinessException("该车辆在MDM中不存在");
        }
        if(!remoteTransMeans.isAvaliable()) {
            throw new BusinessException("MDM中该车辆为非正常车辆");
        }
        
        // 2. lock mdm vehicle
        genericDaoSupport.update(remoteTransMeans.lock(type, Constants.MDM_LOCK_USERID));
        
        // 3. create lock record
        VehicleSourceLockRecord record = new VehicleSourceLockRecord(vehicleSource, LockStatus.LOCKED, description, operator);
        genericDaoSupport.save(record);
        
    }
    
    /**
     * @param vehicleSource
     * @param operator
     * @param description
     */
    private void unlockVehicle(VehicleSource vehicleSource, Long operator, String description) {
        
        Date now = new Date();
        
        // 1. unlock fms vehicle
        genericDaoSupport.update(vehicleSource.unlock(operator, now));
        
        String vehicleCode = vehicleSource.getCode();
        RemoteTransMeans remoteTransMeans = remoteTransMeansDao.loadByCode(vehicleCode);
        
        if(remoteTransMeans == null) {
            throw new BusinessException("该车辆在MDM中不存在");
        }
        if(!remoteTransMeans.isAvaliable()) {
            throw new BusinessException("MDM中该车辆为非正常车辆");
        }
        
        // 2. lock mdm vehicle
        genericDaoSupport.update(remoteTransMeans.unlock(Constants.MDM_LOCK_USERID));
        
        // 3. create lock record
        VehicleSourceLockRecord record = new VehicleSourceLockRecord(vehicleSource, LockStatus.UNLOCKED, description, operator);
        genericDaoSupport.save(record);
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#count(com.demo2do.fms.admin.entity.security.Login)
     */
    public int count(Login login) {
        if (login.isSupplier()) {
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            return trailerDao.count(supplier);
        }
        return trailerDao.count();
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#list(com.demo2do.fms.admin.entity.security.Login, com.demo2do.core.entity.page.Page)
     */
    public List<Trailer> list(Login login, Page page) {
        
        page.calculate();
        
        if (login.isSupplier()) {
            
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            return trailerDao.list(supplier, page.getBeginIndex(), page.getEveryPage());
            
        } else {
            return trailerDao.list(page.getBeginIndex(), page.getEveryPage());
        }
        
    }
    
    /**
     * @param search
     * @return
     */
    public List<Map<String, Object>> export(TrailerReportSearch search) {
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        RowMapper<Map<String, Object>> rowMapper = search.buildRowMapper();
        return fmsJdbcSupport.queryForList(sentence, parameters, rowMapper);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#countSub(com.demo2do.fms.admin.entity.security.Login)
     */
    public int countSub(Login login) {
        
        if (!login.isSupplier()) {  // 总部或分区用户, 拿所有整车
            
            return trailerDao.count();
            
        } else {                    // 一级供应商， 拿所属二级供应商整车
            
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(supplier);
            
            if (org.apache.commons.collections.CollectionUtils.isEmpty(supplierIds)) {
                return 0;
            }
            return trailerDao.countBySupplierIds(supplierIds);
            
        }
        
    }
    
    public List<Trailer> listSub(Login login, Page page) {
        
        page.calculate();
        
        if (!login.isSupplier()) {  // 总部或分区用户, 拿所有整车
            
            return trailerDao.list(page.getBeginIndex(), page.getEveryPage());
            
        } else {                    // 一级供应商， 拿所属二级供应商整车
            
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(supplier);
            return trailerDao.listBySupplierIds(supplierIds, page.getBeginIndex(), page.getEveryPage());
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#countSub(com.demo2do.fms.admin.entity.search.supplier.TrailerSearch, com.demo2do.fms.admin.entity.security.Login)
     */
    public int countSub(TrailerSearch search, Login login) {
        
        if (login.isSupplier()) {
            Supplier loginSupplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(loginSupplier);
            search.addSupplierId(supplierIds);
        }
        
        // 1. build sentence
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        
        // 2. do query
        return genericDaoSupport.count(sentence, parameters);
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#searchSub(com.demo2do.fms.admin.entity.search.supplier.TrailerSearch, com.demo2do.fms.admin.entity.security.Login, com.demo2do.core.entity.page.Page)
     */
    @SuppressWarnings("unchecked")
    public List<Trailer> searchSub(TrailerSearch search, Login login, Page page) {
        
        if (login.isSupplier()) {
            Supplier loginSupplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(loginSupplier);
            search.addSupplierId(supplierIds);
        }
        
        // 1. build sentence
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        
        // 2. do query
        List<Trailer> trailers = genericDaoSupport.searchForList(sentence, parameters, page.getBeginIndex(), page.getEveryPage());
        page.calculate();
        return trailers;
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#countAudit(com.demo2do.fms.admin.entity.security.Login, com.demo2do.fms.entity.vehicle.DeclareStatus)
     */
    public int countAudit(Login login, DeclareStatus declareStatus) {
        
        List<AuditStatus> statuses = Arrays.asList(AuditStatus.AUDITING, AuditStatus.MODIFIED);
        
        if (!login.isSupplier()) {  // 非供方用户, 拿所有供应商下整车
            
            return trailerDao.count(statuses, declareStatus);
            
        } else {                    // 一级供应商， 拿所属二级供应商整车
            
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(supplier);
            if (org.apache.commons.collections.CollectionUtils.isEmpty(supplierIds)) {
                return 0;
            }
            return trailerDao.countBySupplierIds(supplierIds, statuses, declareStatus);
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#listAudit(com.demo2do.fms.admin.entity.security.Login, com.demo2do.fms.entity.vehicle.DeclareStatus, com.demo2do.core.entity.page.Page)
     */
    public List<Trailer> listAudit(Login login, DeclareStatus declareStatus, Page page) {
        
        List<AuditStatus> statuses = Arrays.asList(AuditStatus.AUDITING, AuditStatus.MODIFIED);
        
        page.calculate();
        
        if (!login.isSupplier()) {  // 非供方用户, 拿所有牵引车
            
            return trailerDao.list(statuses, declareStatus, page.getBeginIndex(), page.getEveryPage());
            
        } else {                    // 一级供应商， 拿所属二级供应商整车
            
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(supplier);
            return trailerDao.listBySupplierIds(supplierIds, statuses, declareStatus, page.getBeginIndex(), page.getEveryPage());
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#adoptAuditing(com.demo2do.fms.entity.vehicle.Trailer, java.lang.Long)
     */
    public void adoptAuditing(Trailer trailer, Long operator) {
        
        // 1.1 audit trailer
        genericDaoSupport.update(trailer.adoptAuditing(operator));
        
        // 1.2 save trailer_event
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.AUDIT_ADOPT, operator));
        
        if (trailer.isVam()) {
            
            // 2.1 unlock vehicle_source, when tractor and trailer is qualified
            VehicleSource vehicleSource = vehicleSourceDao.loadByTrailer(trailer.getId());
            if (vehicleSource != null && vehicleSource.couldUnlock()) {
                Tractor tractor = genericDaoSupport.get(Tractor.class, vehicleSource.getTractorId());
                if (tractor.getStatus().isQualified() || tractor.getStatus().isModified()) {
                    this.unlockVehicle(vehicleSource, operator, "牵引车审核通过解锁");
                }
            }
            
            // 2.2 send audit_status to vam
            vamHandler.syncVehicleStatus("trailer", null, trailer);
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#adoptModified(com.demo2do.fms.entity.vehicle.Trailer, java.lang.Long)
     */
    public void adoptModified(Trailer trailer, Long operator) {
        
        // 1. 更新牵引车基本信息
        TrailerModifiedVersion version = trailerModifiedVersionDao.load(trailer.getId(), ModifiedStatus.AUDITING);
        genericDaoSupport.update(trailer.adoptModified(version, operator));
        
        // 2. 更新Version为合格状态
        trailerModifiedVersionDao.update(ModifiedStatus.QUALIFIED, trailer.getId(), ModifiedStatus.AUDITING);
        
        // 3. save trailer_event
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.CHANGE_ADOPT, operator));
        
        // 4. send audit_status to vam
        if (trailer.isVam()) {
            vamHandler.syncVehicleStatus("trailer", null, trailer);
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#rejectAudit(com.demo2do.fms.entity.vehicle.Trailer, java.lang.Long)
     */
    public void rejectAudit(Trailer trailer, Long operator) {
        
        // 1. reject audit
        genericDaoSupport.update(trailer.rejectAuditing(operator));
        
        // 2. save trailer_event
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.AUDIT_REJECT, operator));
        
        // 3. send audit_status to vam
        if (trailer.isVam()) {
            vamHandler.syncVehicleStatus("trailer", null, trailer);
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#rejectModified(com.demo2do.fms.entity.vehicle.Trailer, java.lang.Long)
     */
    public void rejectModified(Trailer trailer, Long operator) {
        
        // 1. reject change
        genericDaoSupport.update(trailer.rejectAuditing(operator));
        
        // 2. 更新变更版本为驳回
        trailerModifiedVersionDao.update(ModifiedStatus.REJECTED, trailer.getId(), ModifiedStatus.AUDITING);
        
        // 3. save trailer_event
        genericDaoSupport.save(new TrailerEvent(trailer, TrailerEventType.CHANGE_REJECT, operator));
        
        // 4. send audit_status to vam
        if (trailer.isVam()) {
            vamHandler.syncVehicleStatus("trailer", null, trailer);
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#list(com.demo2do.fms.entity.supplier.common.Supplier, com.demo2do.fms.entity.vehicle.AuditStatus)
     */
    public List<Trailer> list(Supplier supplier, AuditStatus status) {
        return trailerDao.list(supplier, status);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TrailerService#listUnbind(com.demo2do.fms.entity.supplier.common.Supplier, com.demo2do.fms.entity.vehicle.AuditStatus)
     */
    public List<Trailer> listUnbind(Supplier supplier, AuditStatus status) {
        return trailerDao.list(supplier, status, false);
    }
    
}
